<template>
  <div>
    <script ref="script" :name="name" type="text/plain"></script>
    <lb-upload
      type="more"
      :fileType="fileType"
      :visibles.sync="showDialog"
      :isIframe="isIframe"
      multilineType="more"
      @selectedFiles="getChoiceFiles"
    ></lb-upload>
  </div>
</template>

<script>
import LoadEvent from '@/utils/Event.js'
import Debounce from '@/utils/Debounce.js'
import Bus from '@/Bus'
import { mapGetters } from 'vuex'
export default {
  name: 'VueUeditorWrap',
  data () {
    return {
      showDialog: false,
      fileType: 'image',
      isIframe: false,
      status: 0,
      initValue: '',
      defaultConfig: {
        // VUE CLI 3 会添加 process.env.BASE_URL 的环境变量，而 VUE CLI 2 没有，所以借此设置 UEDITOR_HOME_URL，能涵盖大部分 Vue 开发者的使用场景
        UEDITOR_HOME_URL: process.env.NODE_ENV === 'production' ? window.lbConfig.jsPath + 'static/Ueditor/' : '/static/Ueditor/',
        enableAutoSave: false,
        // 编辑器不自动被内容撑高
        autoHeightEnabled: false,
        // 初始容器高度
        initialFrameHeight: 500,
        // 初始容器宽度
        initialFrameWidth: 'auto',
        // 上传文件接口（这个地址是我为了方便各位体验文件上传功能搭建的临时接口，请勿在生产环境使用！！！）
        serverUrl: 'http://35.201.165.105:8000/controller.php',
        // UEditor 资源文件的存放路径，如果你使用的是 vue-cli 生成的项目，通常不需要设置该选项，vue-ueditor-wrap 会自动处理常见的情况，如果需要特殊配置，参考下方的常见问题2
        // UEDITOR_HOME_URL: '/static/Ueditor/',
        zIndex: 5,
        topOffset: false,
        autoFloatEnabled: false,
        toolbars: []
      },
      thatUeditor: {}
    }
  },
  props: {
    // v-model 实现方式
    mode: {
      type: String,
      default: 'observer',
      validator: function (value) {
        // 1. observer 借助 MutationObserver API https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
        // 2. listener 借助 UEditor 的 contentChange 事件 https://ueditor.baidu.com/doc/#UE.Editor:contentChange
        return ['observer', 'listener'].indexOf(value) !== -1
      }
    },
    value: {
      type: String,
      default: ''
    },
    config: {
      type: Object,
      default: function () {
        return {}
      }
    },
    init: {
      type: Function,
      default: function () {
        return () => { }
      }
    },
    destroy: {
      type: Boolean,
      default: false
    },
    ueditorType: {
      type: Number,
      default: 1
    },
    name: {
      type: String,
      default: ''
    },
    observerDebounceTime: {
      type: Number,
      default: 50,
      validator: function (value) {
        return value >= 20
      }
    },
    observerOptions: {
      type: Object,
      default: function () {
        // https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit
        return {
          attributes: true, // 是否监听 DOM 元素的属性变化
          attributeFilter: ['src', 'style', 'type', 'name'], // 只有在该数组中的属性值的变化才会监听
          characterData: true, // 是否监听文本节点
          childList: true, // 是否监听子节点
          subtree: true // 是否监听后代元素
        }
      }
    },
    // 本组件提供对普通 Vue 项目和 Nuxt 项目开箱即  用的支持，但如果是自己搭建的 Vue SSR 项目，可能需要自行区分是客户端还是服务端环境并跳过环境检测，直接初始化
    forceInit: {
      type: Boolean,
      default: false
    }
  },
  created () {
    Bus.$on('showLbUpload', (obj) => {
      console.log(obj)
      this.thatUeditor = obj.ueditor
      let { uploadStatus } = this
      if (uploadStatus) {
        return false
      } else {
        this.showDialog = obj.val
        this.isIframe = true
        this.fileType = obj.fileType
        this.$store.commit('handleUploadStatus', obj.val)
      }
    })
  },
  computed: {
    mixedConfig () {
      return Object.assign({}, this.defaultConfig, this.config)
    },
    ...mapGetters(['uploadStatus'])
  },
  methods: {
    // 添加自定义按钮（自定义按钮，自定义弹窗等操作从 2.2.0 版本 开始不再考虑直接集成，这会使得组件和 UEditor 过度耦合，但为了兼容一些老版用户的写法，这个方法依然保留）
    registerButton ({ name, icon, tip, handler, index, UE = window.UE }) {
      UE.registerUI(name, (editor, name) => {
        editor.registerCommand(name, {
          execCommand: () => {
            handler(editor, name)
          }
        })
        const btn = new UE.ui.Button({
          name,
          title: tip,
          cssRules: `background-image: url(${icon}) !important;background-size: cover;`,
          onclick () {
            editor.execCommand(name)
          }
        })
        editor.addListener('selectionchange', () => {
          const state = editor.queryCommandState(name)
          if (state === -1) {
            btn.setDisabled(true)
            btn.setChecked(false)
          } else {
            btn.setDisabled(false)
            btn.setChecked(state)
          }
        })
        return btn
      }, index, this.id)
    },
    // 实例化编辑器
    _initEditor () {
      let toolbars = [
        'fullscreen', 'source', '|',
        'undo', 'redo', '|',
        'bold', 'italic', 'underline', 'strikethrough', 'removeformat', 'formatmatch', 'autotypeset', 'pasteplain', '|',
        'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', '|',
        'fontfamily', 'fontsize', '|',
        'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify'
      ]
      let arr = {
        // 1: ['|', 'lbinsertimage', 'lbinsertvideo', 'lbinsertmusic'],
        1: ['|', 'lbinsertimage', 'lbinsertvideo'],
        2: [],
        3: ['|', 'lbinsertimage']
      }
      if (arr[this.ueditorType].length > 0) {
        arr[this.ueditorType].map(item => {
          toolbars.push(item)
        })
      }
      this.defaultConfig.toolbars = [toolbars]
      this.$refs.script.id = this.id = 'editor_' + Math.random().toString(16).slice(-6) // 这么做是为了支持 Vue SSR，因为如果把 id 属性放在 data 里会导致服务端和客户端分别计算该属性的值，而造成 id 不匹配无法初始化的 BUG
      this.init()
      this.$emit('beforeInit', this.id, this.mixedConfig)
      this.editor = window.UE.getEditor(this.id, this.mixedConfig)
      this.editor.addListener('ready', () => {
        if (this.status === 2) { // 使用 keep-alive 组件会出现这种情况
          this.editor.setContent(this.value)
        } else {
          this.status = 2
          this.$emit('ready', this.editor)
          this.editor.setContent(this.initValue)
        }
        if (this.mode === 'observer' && window.MutationObserver) {
          this._observerChangeListener()
        } else {
          this._normalChangeListener()
        }
      })
    },
    // 检测依赖,确保 UEditor 资源文件已加载完毕
    _checkDependencies () {
      return new Promise((resolve, reject) => {
        // 判断ueditor.config.js和ueditor.all.js是否均已加载(仅加载完ueditor.config.js时UE对象和UEDITOR_CONFIG对象存在,仅加载完ueditor.all.js时UEDITOR_CONFIG对象存在,但为空对象)
        let scriptsLoaded = !!window.UE && !!window.UEDITOR_CONFIG && Object.keys(window.UEDITOR_CONFIG).length !== 0 && !!window.UE.getEditor
        if (scriptsLoaded) {
          resolve()
        } else if (window['$loadEnv']) { // 利用订阅发布，确保同时渲染多个组件时，不会重复创建script标签
          window['$loadEnv'].on('scriptsLoaded', () => {
            resolve()
          })
        } else {
          window['$loadEnv'] = new LoadEvent()
          // 如果在其他地方只引用ueditor.all.min.js，在加载ueditor.config.js之后仍需要重新加载ueditor.all.min.js，所以必须确保ueditor.config.js已加载
          this._loadConfig().then(() => this._loadCore()).then(() => {
            resolve()
            window['$loadEnv'].emit('scriptsLoaded')
          })
        }
      })
    },
    _loadConfig () {
      return new Promise((resolve, reject) => {
        if (window.UE && window.UEDITOR_CONFIG && Object.keys(window.UEDITOR_CONFIG).length !== 0) {
          resolve()
          return
        }
        let configScript = document.createElement('script')
        configScript.type = 'text/javascript'
        configScript.src = this.mixedConfig.UEDITOR_HOME_URL + 'ueditor.config.js'
        document.getElementsByTagName('head')[0].appendChild(configScript)
        configScript.onload = function () {
          if (window.UE && window.UEDITOR_CONFIG && Object.keys(window.UEDITOR_CONFIG).length !== 0) {
            resolve()
          } else {
            console.warn('加载ueditor.config.js失败,请检查您的配置地址UEDITOR_HOME_URL填写是否正确!\n', configScript.src)
          }
        }
      })
    },
    _loadCore () {
      return new Promise((resolve, reject) => {
        if (window.UE && window.UE.getEditor) {
          resolve()
          return
        }
        let coreScript = document.createElement('script')
        coreScript.type = 'text/javascript'
        coreScript.src = this.mixedConfig.UEDITOR_HOME_URL + 'ueditor.all.min.js'
        document.getElementsByTagName('head')[0].appendChild(coreScript)
        coreScript.onload = function () {
          if (window.UE && window.UE.getEditor) {
            resolve()
          } else {
            console.warn('加载ueditor.all.min.js失败,请检查您的配置地址UEDITOR_HOME_URL填写是否正确!\n', coreScript.src)
          }
        }
      })
    },
    // 设置内容
    _setContent (value) {
      value === this.editor.getContent() || this.editor.setContent(value)
    },
    contentChangeHandler () {
      this.$emit('input', this.editor.getContent())
    },
    // 基于 UEditor 的 contentChange 事件
    _normalChangeListener () {
      this.editor.addListener('contentChange', this.contentChangeHandler)
    },
    // 基于 MutationObserver API
    _observerChangeListener () {
      const changeHandle = (mutationsList) => {
        if (this.editor.document.getElementById('baidu_pastebin')) {
          return
        }
        this.$emit('input', this.editor.getContent())
      }
      // 函数防抖
      this.observer = new MutationObserver(Debounce(changeHandle, this.observerDebounceTime))
      this.observer.observe(this.editor.body, this.observerOptions)
    },
    getChoiceFiles (files) {
      console.log(files)
      for (let i = 0, len = files.length; i < len; i++) {
        let insetHtml
        if (files[i].type === 1) {
          insetHtml = `<img src="${files[i].url}" style="box-sizing: border-box !important; overflow-wrap: break-word !important; visibility: visible !important;"/>`
        } else if (files[i].type === 2) {
          insetHtml = `<audio src="${files[i].url}" controls="controls"></audio>`
        } else if (files[i].type === 3) {
          insetHtml = `<img width='343' height='220' class='edui-upload-video vjs-default-skin video-js' _url='${files[i].url}' style="background:url(https://lbqny.migugu.com/admin/public/videologo.gif) no-repeat center center; border:1px solid gray;" />`
        } else if (files[i].type === 5) { // iframe标签
          insetHtml = files[i].url
        }
        this.thatUeditor.execCommand('insertHtml', insetHtml)
      }
    }
  },
  deactivated () {
    this.editor && this.editor.removeListener('contentChange', this.contentChangeHandler)
    this.observer && this.observer.disconnect()
  },
  beforeDestroy () {
    if (this.destroy && this.editor && this.editor.destroy) {
      this.editor.destroy()
    }
    if (this.observer && this.observer.disconnect) {
      this.observer.disconnect()
    }
    this.$store.commit('handleUploadStatus', false)
    Bus.$off('showLbUpload')
  },
  // v-model语法糖实现
  watch: {
    value: {
      handler (value) {
        // 0: 尚未初始化 1: 开始初始化但尚未ready 2 初始化完成并已ready
        value = value.replace('font-family: &quot;Microsoft Yahei&quot;;', 'font-family: Microsoft Yahei;')
        switch (this.status) {
          case 0:
            this.status = 1
            this.initValue = value;
            // 判断执行环境是服务端还是客户端，这里的 process.client 是 Nuxt 添加的环境变量
            (this.forceInit || (typeof process !== 'undefined' && process.client) || typeof window !== 'undefined') && this._checkDependencies().then(() => {
              this.$refs.script ? this._initEditor() : this.$nextTick(() => this._initEditor())
            })
            break
          case 1:
            this.initValue = value
            break
          case 2:
            this._setContent(value)
            break
          default:
            break
        }
      },
      immediate: true
    },
    showDialog (val) {
      if (!val) {
        this.$store.commit('handleUploadStatus', val)
      }
    }
  }
}
</script>
